/**
 * @file    elog_async.c
 * @brief   EasyLogger异步输出模块实现
 * @details 本文件实现了EasyLogger的异步输出功能，包括：
 *          - 基于环形缓冲区的异步日志存储
 *          - pthread线程的异步输出处理
 *          - 支持行模式和块模式的日志输出
 *          - 异步输出的初始化和反初始化
 *          - 异步输出模式的启用和禁用控制
 */

#include <elog.h>                                                               /* EasyLogger核心头文件 */
#include <string.h>                                                             /* 标准字符串操作函数 */

#ifdef ELOG_ASYNC_OUTPUT_ENABLE

#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
#include <pthread.h>                                                            /* POSIX线程库 */
#include <sched.h>                                                              /* 调度策略相关 */
#include <semaphore.h>                                                          /* 信号量操作 */

/* ----------------------------------- 异步输出线程配置 --------------------------------------- */
/* 线程默认栈大小 */
#ifndef ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE
#if PTHREAD_STACK_MIN > 4*1024
#define ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE     PTHREAD_STACK_MIN
#else
#define ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE     (1*1024)
#endif
/* 线程默认优先级 */
#ifndef ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY
#define ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY       (sched_get_priority_max(SCHED_RR) - 1)
#endif
/* 输出线程轮询获取日志的缓冲区大小 */
#ifndef ELOG_ASYNC_LINE_OUTPUT
#ifndef ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE
#define ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE         (ELOG_ASYNC_OUTPUT_BUF_SIZE - 4)
#endif
#else
#ifndef ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE
#define ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE         (ELOG_LINE_BUF_SIZE - 4)
#endif
#endif
#endif /* ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE */

/* ----------------------------------- 异步输出线程相关变量 ----------------------------------- */
static sem_t output_notice;                                                     /* 异步输出日志通知信号量 */
static pthread_t async_output_thread;                                           /* 异步输出pthread线程句柄 */
#endif /* ELOG_ASYNC_OUTPUT_USING_PTHREAD */

/* ----------------------------------- 异步输出配置宏定义 ------------------------------------- */
/* 异步模式的最高输出级别，其他级别将同步输出 */
#ifdef ELOG_ASYNC_OUTPUT_LVL
#define OUTPUT_LVL                               ELOG_ASYNC_OUTPUT_LVL
#else
#define OUTPUT_LVL                               ELOG_LVL_ASSERT
#endif /* ELOG_ASYNC_OUTPUT_LVL */

/* 异步输出模式的缓冲区大小 */
#ifdef ELOG_ASYNC_OUTPUT_BUF_SIZE
#define OUTPUT_BUF_SIZE                          ELOG_ASYNC_OUTPUT_BUF_SIZE
#else
#define OUTPUT_BUF_SIZE                          (ELOG_LINE_BUF_SIZE * 10)
#endif /* ELOG_ASYNC_OUTPUT_BUF_SIZE */

/* ----------------------------------- 异步输出模块状态变量 ------------------------------------- */
static bool init_ok = false;                                                    /* 初始化完成标志 */
#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
static bool thread_running = false;                                             /* 线程运行标志 */
#endif
static bool is_enabled = false;                                                 /* 异步输出模式启用标志 */

/* ----------------------------------- 异步输出环形缓冲区相关变量 ------------------------------- */
static char log_buf[OUTPUT_BUF_SIZE] = { 0 };                                   /* 异步输出模式的环形缓冲区 */
static size_t write_index = 0;                                                  /* 日志环形缓冲区写索引 */
static size_t read_index = 0;                                                   /* 日志环形缓冲区读索引 */
static bool buf_is_full = false;                                                /* 日志环形缓冲区满标志 */
static bool buf_is_empty = true;                                                /* 日志环形缓冲区空标志 */

/* ----------------------------------- 外部接口函数声明 ----------------------------------------- */
extern void elog_port_output(const char *log, size_t size);                     /* 底层日志输出接口 */
extern void elog_output_lock(void);                                             /* 输出锁定接口 */
extern void elog_output_unlock(void);                                           /* 输出解锁接口 */

/* ----------------------------------- 环形缓冲区管理函数 --------------------------------------- */
/**
 * @brief  获取异步输出环形缓冲区已使用的字节数
 * @param  无
 * @return 已使用的字节数
 * @note   计算环形缓冲区中已写入但尚未读取的日志数据大小
 */
static size_t elog_async_get_buf_used(void)
{
    /* 写索引大于读索引，直接计算差值 */
    if (write_index > read_index)
    {
        return write_index - read_index;                                        /* 返回已使用字节数 */
    }
    else
    {
        /* 写索引小于等于读索引，需要考虑环形缓冲区状态 */
        if (!buf_is_full && !buf_is_empty)
        {
            return OUTPUT_BUF_SIZE - (read_index - write_index);                /* 计算跨越边界的已使用字节数 */
        }
        else if (buf_is_full)
        {
            return OUTPUT_BUF_SIZE;                                             /* 缓冲区满，返回总大小 */
        }
        else
        {
            return 0;                                                           /* 缓冲区空，返回0 */
        }
    }
}

/**
 * @brief  获取异步输出环形缓冲区剩余空间
 * @param  无
 * @return 剩余可用空间字节数
 * @note   计算环形缓冲区中可用于写入新日志数据的剩余空间大小
 */
static size_t async_get_buf_space(void)
{
    return OUTPUT_BUF_SIZE - elog_async_get_buf_used();                         /* 总大小减去已使用大小 */
}

/**
 * @brief  将日志数据写入异步输出环形缓冲区
 * @param  log 要写入的日志数据缓冲区指针
 * @param  size 要写入的日志数据大小
 * @return 实际写入的字节数
 * @note   如果缓冲区空间不足，超出部分的日志将被丢弃
 */
static size_t async_put_log(const char *log, size_t size)
{
    size_t space = 0;                                                           /* 可用空间大小 */

    space = async_get_buf_space();                                              /* 获取缓冲区剩余空间 */
    
    /* 检查是否有可用空间 */
    if (!space)
    {
        size = 0;                                                               /* 无空间，设置写入大小为0 */
        goto __exit;                                                            /* 跳转到退出标签 */
    }
    
    /* 空间不足时丢弃部分日志 */
    if (space <= size)
    {
        size = space;                                                           /* 调整写入大小为可用空间 */
        buf_is_full = true;                                                     /* 标记缓冲区为满 */
    }

    /* 判断是否需要分段写入 */
    if (write_index + size < OUTPUT_BUF_SIZE)
    {
        /* 一次性写入，不跨越缓冲区边界 */
        memcpy(log_buf + write_index, log, size);                               /* 复制日志数据到缓冲区 */
        write_index += size;                                                    /* 更新写索引 */
    }
    else
    {
        /* 分两段写入，跨越缓冲区边界 */
        memcpy(log_buf + write_index, log, OUTPUT_BUF_SIZE - write_index);      /* 写入第一段到缓冲区末尾 */
        memcpy(log_buf, log + OUTPUT_BUF_SIZE - write_index,                    /* 写入第二段到缓冲区开头 */
                size - (OUTPUT_BUF_SIZE - write_index));
        write_index += size - OUTPUT_BUF_SIZE;                                  /* 更新写索引，处理环形 */
    }

    buf_is_empty = false;                                                       /* 标记缓冲区非空 */

__exit:
    return size;                                                                /* 返回实际写入大小 */
}

#ifdef ELOG_ASYNC_LINE_OUTPUT
/**
 * @brief  从异步输出环形缓冲区获取行日志数据
 * @param  log 用于存储获取日志的缓冲区指针
 * @param  size 日志缓冲区大小
 * @return 实际获取的日志大小
 * @note   按行读取日志数据，如果找不到换行符则复制所有可用日志
 */
size_t elog_async_get_line_log(char *log, size_t size)
{
    size_t used = 0, cpy_log_size = 0;                                          /* 已使用大小和复制大小 */
    
    /* 锁定输出 */
    elog_output_lock();
    used = elog_async_get_buf_used();                                           /* 获取缓冲区已使用大小 */

    /* 检查是否有日志数据 */
    if (!used || !size)
    {
        goto __exit;                                                            /* 无日志或无空间，直接退出 */
    }
    
    /* 调整读取大小 */
    if (used <= size)
    {
        size = used;                                                            /* 可用日志小于请求大小 */
    }

    /* 判断是否需要分段读取 */
    if (read_index + size < OUTPUT_BUF_SIZE)
    {
        /* 一次性读取，不跨越缓冲区边界 */
        cpy_log_size = elog_cpyln(log, log_buf + read_index, size);             /* 按行复制日志数据 */
        read_index += cpy_log_size;                                             /* 更新读索引 */
    }
    else
    {
        /* 分两段读取，跨越缓冲区边界 */
        cpy_log_size = elog_cpyln(log, log_buf + read_index, OUTPUT_BUF_SIZE - read_index); /* 读取第一段 */
        if (cpy_log_size == OUTPUT_BUF_SIZE - read_index)
        {
            /* 第一段读取完整，继续读取第二段 */
            cpy_log_size += elog_cpyln(log + cpy_log_size, log_buf, size - cpy_log_size);   /* 读取第二段 */
            read_index += cpy_log_size - OUTPUT_BUF_SIZE;                       /* 更新读索引，处理环形 */
        }
        else
        {
            read_index += cpy_log_size;                                         /* 更新读索引 */
        }
    }

    /* 更新缓冲区状态标志 */
    if (used == cpy_log_size)
    {
        buf_is_empty = true;                                                    /* 标记缓冲区为空 */
    }

    if (cpy_log_size)
    {
        buf_is_full = false;                                                    /* 标记缓冲区非满 */
    }

__exit:
    /* 解锁输出 */
    elog_output_unlock();
    return cpy_log_size;                                                        /* 返回实际复制大小 */
}
#else
/**
 * @brief  从异步输出环形缓冲区获取日志数据
 * @param  log 用于存储获取日志的缓冲区指针
 * @param  size 要获取的日志数据大小
 * @return 实际获取的日志大小
 * @note   从环形缓冲区中读取指定大小的日志数据，处理索引循环
 */
size_t elog_async_get_log(char *log, size_t size)
{
    size_t used = 0;                                                            /* 已使用大小 */
    
    /* 锁定输出 */
    elog_output_lock();
    used = elog_async_get_buf_used();                                           /* 获取缓冲区已使用大小 */
    
    /* 检查是否有日志数据 */
    if (!used || !size)
    {
        size = 0;                                                               /* 无日志或无空间，设置返回大小为0 */
        goto __exit;                                                            /* 跳转到退出标签 */
    }
    
    /* 调整读取大小 */
    if (used <= size)
    {
        size = used;                                                            /* 可用日志小于请求大小 */
        buf_is_empty = true;                                                    /* 标记缓冲区为空 */
    }

    /* 判断是否需要分段读取 */
    if (read_index + size < OUTPUT_BUF_SIZE)
    {
        /* 一次性读取，不跨越缓冲区边界 */
        memcpy(log, log_buf + read_index, size);                                /* 复制日志数据 */
        read_index += size;                                                     /* 更新读索引 */
    }
    else
    {
        /* 分两段读取，跨越缓冲区边界 */
        memcpy(log, log_buf + read_index, OUTPUT_BUF_SIZE - read_index);        /* 读取第一段到缓冲区末尾 */
        memcpy(log + OUTPUT_BUF_SIZE - read_index, log_buf,                     /* 读取第二段从缓冲区开头 */
                size - (OUTPUT_BUF_SIZE - read_index));
        read_index += size - OUTPUT_BUF_SIZE;                                   /* 更新读索引，处理环形 */
    }

    buf_is_full = false;                                                        /* 标记缓冲区非满 */

__exit:
    /* 解锁输出 */
    elog_output_unlock();
    return size;                                                                /* 返回实际读取大小 */
}
#endif /* ELOG_ASYNC_LINE_OUTPUT */

/* ----------------------------------- 异步输出接口函数 ----------------------------------------- */
/**
 * @brief  异步输出日志接口函数
 * @param  level 日志级别
 * @param  log 要输出的日志数据指针
 * @param  size 日志数据大小
 * @note   根据日志级别决定是将日志放入异步缓冲区还是直接同步输出
 *         当异步模式启用且日志级别达到异步输出阈值时，将日志放入缓冲区并通知输出线程
 */
void elog_async_output(uint8_t level, const char *log, size_t size)
{
    /* 当未定义ELOG_ASYNC_OUTPUT_USING_PTHREAD时，此函数必须由用户实现 */
    extern void elog_async_output_notice(void);
    size_t put_size;                                                            /* 实际写入大小 */

    /* 检查异步输出是否启用 */
    if (is_enabled)
    {
        /* 检查日志级别是否达到异步输出阈值 */
        if (level >= OUTPUT_LVL)
        {
            put_size = async_put_log(log, size);                                /* 将日志放入异步缓冲区 */
            /* 通知输出线程有新日志 */
            if (put_size > 0)
            {
                elog_async_output_notice();                                     /* 发送通知信号 */
            }
        }
        else
        {
            elog_port_output(log, size);                                        /* 级别不够，直接同步输出 */
        }
    }
    else
    {
        elog_port_output(log, size);                                            /* 异步模式未启用，直接同步输出 */
    }
}

#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
/**
 * @brief  异步输出通知函数
 * @param  无
 * @note   通过信号量通知异步输出线程有新的日志数据需要处理
 */
void elog_async_output_notice(void)
{
    sem_post(&output_notice);                                                   /* 发送信号量通知 */
}

/**
 * @brief  异步输出线程主函数
 * @param  arg 线程参数（未使用）
 * @return 线程返回值（始终为NULL）
 * @note   异步输出线程的主循环函数，等待信号量通知后从环形缓冲区读取日志并输出
 *         支持行模式和块模式两种输出方式，持续运行直到线程停止标志被设置
 */
static void *async_output(void *arg)
{
    size_t get_log_size = 0;                                                    /* 获取的日志大小 */
    static char poll_get_buf[ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE];                 /* 轮询获取日志的缓冲区 */

    /* 线程主循环 */
    while(thread_running)
    {
        /* 等待日志通知信号 */
        sem_wait(&output_notice);
        
        /* 轮询获取并输出日志 */
        while(true)
        {
#ifdef ELOG_ASYNC_LINE_OUTPUT
            /* 行模式：按行获取日志 */
            get_log_size = elog_async_get_line_log(poll_get_buf, ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE);
#else
            /* 块模式：按块获取日志 */
            get_log_size = elog_async_get_log(poll_get_buf, ELOG_ASYNC_POLL_GET_LOG_BUF_SIZE);
#endif

            /* 检查是否获取到日志数据 */
            if (get_log_size)
            {
                elog_port_output(poll_get_buf, get_log_size);                   /* 输出日志到底层接口 */
            }
            else
            {
                break;                                                          /* 无更多日志，退出内层循环 */
            }
        }
    }
    return NULL;                                                                /* 线程退出 */
}
#endif

/* ----------------------------------- 异步输出控制函数 ----------------------------------------- */
/**
 * @brief  启用或禁用异步输出模式
 * @param  enabled true: 启用异步输出模式, false: 禁用异步输出模式
 * @note   控制异步输出模式的开关状态。当禁用时，所有日志将直接同步输出
 *         当启用时，符合条件的日志将放入异步缓冲区由后台线程处理
 */
void elog_async_enabled(bool enabled)
{
    is_enabled = enabled;                                                       /* 设置异步输出模式启用标志 */
}

/**
 * @brief 异步输出模式初始化
 * @param  无
 * @return 初始化结果，ELOG_NO_ERR表示成功
 * @note   初始化异步输出模块，包括创建输出线程、初始化信号量、设置线程属性等
 *         如果已经初始化过则直接返回成功。支持pthread和非pthread两种模式
 */
ElogErrCode elog_async_init(void)
{
    ElogErrCode result = ELOG_NO_ERR;                                           /* 初始化结果 */

    /* 检查是否已经初始化 */
    if (init_ok)
    {
        return result;                                                          /* 已初始化，直接返回成功 */
    }

#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
    pthread_attr_t thread_attr;                                                 /* 线程属性 */
    struct sched_param thread_sched_param;                                      /* 线程调度参数 */

    /* 初始化信号量 */
    sem_init(&output_notice, 0, 0);                                             /* 初始化输出通知信号量 */

    thread_running = true;                                                      /* 设置线程运行标志 */

    /* 设置线程属性 */
    pthread_attr_init(&thread_attr);                                            /* 初始化线程属性 */
    //pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);       /* 设置为分离状态（可选） */
    pthread_attr_setstacksize(&thread_attr, ELOG_ASYNC_OUTPUT_PTHREAD_STACK_SIZE);  /* 设置栈大小 */
    pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);                        /* 设置调度策略为轮转 */
    thread_sched_param.sched_priority = ELOG_ASYNC_OUTPUT_PTHREAD_PRIORITY;     /* 设置线程优先级 */
    pthread_attr_setschedparam(&thread_attr, &thread_sched_param);              /* 应用调度参数 */
    
    /* 创建异步输出线程 */
    pthread_create(&async_output_thread, &thread_attr, async_output, NULL);     /* 创建线程 */
    pthread_attr_destroy(&thread_attr);                                         /* 销毁线程属性对象 */
#endif

    init_ok = true;                                                             /* 标记初始化完成 */

    return result;                                                              /* 返回初始化结果 */
}

/**
 * @brief  异步输出模式反初始化
 * @param  无
 * @note   停止异步输出线程，清理相关资源包括信号量、线程句柄等
 *         如果未初始化则直接返回。确保线程安全退出并释放所有资源
 */
void elog_async_deinit(void)
{
    /* 检查是否已经初始化 */
    if (!init_ok)
    {
        return ;                                                                /* 未初始化，直接返回 */
    }

#ifdef ELOG_ASYNC_OUTPUT_USING_PTHREAD
    thread_running = false;                                                     /* 设置线程停止标志 */

    elog_async_output_notice();                                                 /* 发送通知信号，唤醒线程 */

    pthread_join(async_output_thread, NULL);                                    /* 等待线程退出 */
    
    sem_destroy(&output_notice);                                                /* 销毁信号量 */
#endif

    init_ok = false;                                                            /* 标记反初始化完成 */
}

#endif /* ELOG_ASYNC_OUTPUT_ENABLE */
